import json
import os
import jsonpickle
from dataclasses import dataclass, fields
from datetime import datetime as datetime_
from datetime import timedelta
from conf import conf
import pandas as pd

from core.constant import *
import tkinter as tk

import smtplib
from email.mime.text import MIMEText
from email.header import Header
from email.utils import formataddr


class RunStatus:
    """
    operate模块中上方展示的模块运行状态，对应的实体类
    """
    def __init__(self, master):
        self.master = master
        self.row1st: str = ""
        self.run_module_ls: list[Module] = []
        self.is_run: bool = False
        self.log_engine = None

    def get_string(self):
        sentence = f"{self.row1st}"
        for run_module in self.run_module_ls:
            sentence += f"\n{run_module.value}: 运行中"
        return sentence

    def start_task(self, module: Module):
        if self.run_module_ls is None:
            self.run_module_ls = [module]
            self.is_run = True
            self.log_engine.emit(self.get_string(), LogName.Operate_run_status)
            print("框架加入第一个任务。")
        else:
            if module in self.run_module_ls:
                raise ValueError("模块已经运行，逻辑错误。")
            else:
                self.run_module_ls.append(module)
                self.log_engine.emit(self.get_string(), LogName.Operate_run_status)
                print("框架加入任务。")

    def end_task(self, module: Module):
        if self.run_module_ls is None:
            raise ValueError("当前无运行的模块，逻辑错误。")
        else:
            if module in self.run_module_ls:
                self.run_module_ls.remove(module)
                print("框架删除任务。")
                self.log_engine.emit(self.get_string(), LogName.Operate_run_status)
                if not self.run_module_ls:
                    self.is_run = False
                    print("框架已不存在运行中的任务。")
            else:
                raise ValueError("当前已运行的模块，逻辑错误。")

    def config_log_engine(self):
        self.log_engine = self.master.file_manager.log_engine
        info = self.get_string()
        self.log_engine.emit(info, LogName.Operate_run_status)


@dataclass
class TickData:
    """
    Tick support contains information about:
        * last trade in market
        * orderbook snapshot
        * intraday market statistics.
    """

    symbol: str
    exchange: Exchange
    datetime: datetime_

    name: str = ""
    volume: float = 0
    turnover: float = 0
    open_interest: float = 0
    last_price: float = 0
    last_volume: float = 0
    limit_up: float = 0
    limit_down: float = 0

    open_price: float = 0
    high_price: float = 0
    low_price: float = 0
    pre_close: float = 0

    bid_price_1: float = 0
    bid_price_2: float = 0
    bid_price_3: float = 0
    bid_price_4: float = 0
    bid_price_5: float = 0

    ask_price_1: float = 0
    ask_price_2: float = 0
    ask_price_3: float = 0
    ask_price_4: float = 0
    ask_price_5: float = 0

    bid_volume_1: float = 0
    bid_volume_2: float = 0
    bid_volume_3: float = 0
    bid_volume_4: float = 0
    bid_volume_5: float = 0

    ask_volume_1: float = 0
    ask_volume_2: float = 0
    ask_volume_3: float = 0
    ask_volume_4: float = 0
    ask_volume_5: float = 0

    localtime: datetime_ = None


@dataclass
class BarData:
    """
    Candlestick bar support of a certain trading period.
    """

    symbol: str
    exchange: Exchange
    datetime: datetime_

    interval: Interval = None
    volume: float = 0
    turnover: float = 0
    open_interest: float = 0
    open_price: float = 0
    high_price: float = 0
    low_price: float = 0
    close_price: float = 0

    factor_sr: pd.Series = None

    def to_sr(self):
        sr_dc = {
            "open": self.open_price,
            "high": self.high_price,
            "low": self.low_price,
            "close": self.close_price,
            "volume": self.volume,
            "turnover": self.turnover
        }

        sr = pd.Series(sr_dc)
        sr.name = self.datetime
        return sr


@dataclass
class OrderData:
    """
    Order support contains information for tracking lastest status
    of a specific order.
    """

    symbol: str
    exchange: Exchange
    orderid: str

    type: OrderType = OrderType.LIMIT
    direction: Direction = None
    offset: Offset = Offset.NONE
    price: float = 0
    volume: float = 0
    traded: float = 0
    commission: float = 0
    status: Status = Status.SUBMITTING
    datetime: datetime_ = None
    lifespan_time: timedelta = None
    reference: str = ""

    def to_sr(self):
        # 创建一个字典，包含所有属性除了dt_close
        data = {field.name: getattr(self, field.name) for field in fields(self) if field.name != "datetime"}

        # 使用dt_close作为Series的name
        sr = pd.Series(data, name=self.datetime)
        return sr

    def to_json_dc(self):
        json_dc = {}
        for attr in self.__dict__:
            value = getattr(self, attr)
            json_dc[attr] = value

        return jsonpickle.encode(json_dc, unpicklable=True, make_refs=False)

    def to_attr(self, json_dc_encode):
        json_dc = jsonpickle.decode(json_dc_encode)
        for name, value in json_dc.items():
            setattr(self, name, value)


@dataclass
class TradeData:
    """
    Trade support contains information of a fill of an order. One order
    can have several trade fills.
    """

    symbol: str
    exchange: Exchange
    orderid: str
    tradeid: str
    direction: Direction = None

    offset: Offset = Offset.NONE
    price: float = 0
    volume: float = 0
    datetime: datetime_ = None


@dataclass
class BTTradeData:
    """
    与TradeData不同，BTTradeData旨在描述一次交易行为，从一个data（symbol）仓位不为零开始，
    到归为零的过程记为一次BT（backtrader）交易。
    """
    symbol: str
    exchange: Exchange

    commission: float = 0
    pnl: float = 0
    is_closed: bool = False
    dt_open: datetime_ = None
    dt_close: datetime_ = None

    def to_sr(self):
        # 创建一个字典，包含所有属性除了dt_close
        data = {field.name: getattr(self, field.name) for field in fields(self) if field.name != "dt_close"}

        # 使用dt_close作为Series的name
        sr = pd.Series(data, name=self.dt_close)
        return sr


@dataclass
class PositionData:
    """
    Positon support is used for tracking each individual position holding.
    """
    # 实际成交或平仓挂单牵扯仓位
    symbol: str
    exchange: Exchange
    direction: Direction

    volume: float = 0
    frozen: float = 0  # 冻结的数量，对于持仓时间限制的品种
    price: float = 0  # 平均持仓价格
    pnl: float = 0
    yd_volume: float = 0  # 预留逻辑，指昨日的仓位数量

    def to_json_dc(self):
        json_dc = {}
        for attr in self.__dict__:
            value = getattr(self, attr)
            json_dc[attr] = value

        return jsonpickle.encode(json_dc, unpicklable=True, make_refs=False)

    def to_attr(self, json_dc_encode):
        json_dc = jsonpickle.decode(json_dc_encode)
        for name, value in json_dc.items():
            setattr(self, name, value)

    def occupy_pos(self, order_data: OrderData):
        if self.frozen + order_data.volume > self.volume:
            raise ValueError("仓位冻结部分加上本次预冻结部分超出仓位实际数量，逻辑错误。")
        else:
            self.frozen += order_data.volume

    def release_pos(self, order_data: OrderData):
        if self.frozen - order_data.volume < 0:
            raise ValueError("仓位冻结部分减去本次预释放部分小于0，逻辑错误。")
        else:
            self.frozen -= order_data.volume

    def close_pos_judge(self, order_data: OrderData):
        if self.direction == order_data.direction:  # 平仓订单方向与相应仓位方向一致
            return False
        elif self.volume - self.frozen >= order_data.volume:
            return True
        else:
            return False

    def open_pos_judge(self, order_data: OrderData):
        if self.direction == order_data.direction:  # 平仓订单方向与相应仓位方向一致
            return True
        else:
            return False

    def before_open_accept(self, order_data: OrderData):
        pass  # 无

    def after_open_cancel(self, order_data: OrderData):
        pass  # 无

    def after_open_deal(self, order_data: OrderData):
        # 改变仓位信息
        # 仓位逻辑
        if self.direction == order_data.direction:
            # 更改价格信息
            self.price = (self.price * self.volume + order_data.price * order_data.volume) / (self.volume + order_data.volume)
            # 更改仓位信息
            self.volume += order_data.traded
        else:
            raise ValueError("before_open_accept未能拦截矛盾的开仓单，逻辑错误。")

    def before_close_accept(self, order_data: OrderData):
        self.occupy_pos(order_data)

    def after_close_cancel(self, order_data: OrderData):
        self.release_pos(order_data)

    def after_close_deal(self, order_data: OrderData):
        # 改变仓位信息
        symbol = order_data.symbol
        if self.volume == order_data.traded:
            # 删除该仓位
            # self.position_dc.pop(order.symbol)
            # 于上一级执行
            return "pop"
        elif self.volume - self.frozen < order_data.traded:
            raise ValueError("仓位数量不足订单数量，无法成交但订单状态已变，逻辑错误。")
        else:
            # 更改盈利信息
            self.pnl = self.pnl * ((self.volume - order_data.volume) / self.volume)
            # 更改仓位信息
            self.volume -= order_data.traded


@dataclass
class AccountData:
    """
    Account support contains information about balance, frozen and
    available.
    """
    # 实际成交或开仓挂单牵扯账户
    account_id: str

    balance: float = 0
    pledge: float = 0
    frozen: float = 0
    fee_rate: float = 0
    fee_fund: float = 0

    def to_json_dc(self):
        json_dc = {}
        for attr in self.__dict__:
            value = getattr(self, attr)
            json_dc[attr] = value

        return jsonpickle.encode(json_dc, unpicklable=True, make_refs=False)

    def to_attr(self, json_dc_encode):
        json_dc = jsonpickle.decode(json_dc_encode)
        for name, value in json_dc.items():
            setattr(self, name, value)

    def open_is_reasonable(self, order_data: OrderData):
        if order_data.offset == Offset.OPEN:
            # 每个order成功发出即可新增冻结资金
            fund = order_data.price * order_data.volume
            self.calculate_fee(order_data)
            if fund + order_data.commission > self.balance:
                return False
            else:
                return True

    def calculate_fee(self, order_data: OrderData):
        order_data.commission = order_data.price*order_data.volume*self.fee_rate

    def before_open_accept(self, order_data: OrderData):
        fund = order_data.price * order_data.volume
        self.balance -= fund
        self.pledge += fund

    def after_open_cancel(self, order_data: OrderData):
        fund = order_data.price * order_data.volume
        self.balance += fund
        self.pledge -= fund

    def before_open_deal(self, order_data: OrderData):
        fund = order_data.price * order_data.volume
        self.balance += fund
        self.pledge -= fund

    def after_open_deal(self, order_data: OrderData):
        # 确定已经返还了委托时的资金！！！
        # 扣除资金
        fund = order_data.price * order_data.volume
        self.balance -= fund
        self.frozen += fund
        # 扣除手续费
        self.calculate_fee(order_data)
        fee_fund = order_data.commission
        self.balance -= fee_fund
        self.fee_fund += fee_fund

    def before_close_accept(self, order_data: OrderData):
        pass

    def after_close_cancel(self, order_data: OrderData):
        pass

    def after_close_deal(self, order_data: OrderData):
        self.calculate_fee(order_data)
        fund = order_data.price * order_data.volume
        self.balance += fund
        self.frozen -= fund
        fee_fund = order_data.commission
        self.balance -= fee_fund
        self.fee_fund += fee_fund

    def update_frozen(self, position_dc: dict[str, PositionData]):
        # 根据当前仓位信息判断浮动盈亏，调整冻结资金的净值
        frozen = 0
        for symbol_o, position_o in position_dc.items():
            frozen += position_o.volume * position_o.price + position_o.pnl
        self.frozen = frozen


@dataclass
class MessageData:
    """
    The fighting support.
    """
    message_id: str = ""
    symbol: str = ""
    price: float = 0
    direction: Direction = Direction.MIDDLE
    status: Status = Status.SUBMITTING
    level: Level = Level.Normal

    content: str = ""
    table: list = None
    datetime: datetime_ = None
    am: type = None

    def to_sr(self):
        sr_dc = {"级别": self.level.value,
                 "标的": self.symbol,
                 "价格": self.price,
                 "方向": self.direction.value,
                 "内容": self.content}
        sr = pd.Series(sr_dc, name=self.datetime.strftime('%Y-%m-%d %H:%M:%S'))
        return sr

    def to_str(self):
        content = (f"级别: {self.level.value},标的: {self.symbol},价格: {self.price}, "
                   f"方向: {self.direction.value}, "
                   f"时间: {self.datetime.strftime('%H:%M:%S')}"
                   f"\n内容: {self.content}")
        return content


@dataclass
class DataDescribe:
    """
    时间序列行情数据
    """
    name: str = ""
    interval_val: Interval = None
    path: str = ""
    symbol_ls: list[str] = None
    merge_columns_ls: list[str] = None
    data_len: int = 0
    data_modified_time: datetime_ = None
    start_end_time_ls: list[datetime_] = None
    category: DataCategory = None

    def get_string(self):
        """实例内容转文本"""
        attr_string = ""
        for name, value in self.__dict__.items():
            if name in ["name"]:
                name = "名称"
                attr_string += f"{name}: {value} | "
            elif name in ["interval_val"]:
                name = "周期"
                attr_string += f"{name}: {value}\n"
            elif name in ["path"]:
                name = "路径"
                # 将路径仅获取替换文本中出现的项目路径部分为...
                dir_path = os.path.dirname(value) + '\\'
                attr_string += f"{name}: {dir_path}\n"
            elif name in ["symbol_ls"]:
                name = "标的"
                attr_string += f"{name}: {value}\n"
            elif name in ["merge_columns_ls"]:
                name = "因子"
                attr_string += f"{name}: {value}\n"
            elif name in ["data_len"]:
                name = "长度"
                attr_string += f"{name}: {value} | "
            elif name in ["category"]:
                name = "类别"
                attr_string += f"{name}: {value}\n"
            elif name in ["data_modified_time"]:
                name = "文件时间"
                attr_string += f"{name}: {value.strftime('%Y-%m-%d %H:%M:%S')}\n"
            elif name in ["start_end_time_ls"]:
                name = "起止时间"
                attr_string += f"{name}: {value[0].strftime('%Y-%m-%d %H:%M:%S')} - {value[1].strftime('%Y-%m-%d %H:%M:%S')} | "
            else:
                name = "其他"
                attr_string += f"{name}: {value}\t"

        return attr_string


class DDQEmail:
    def __init__(self):
        # 163的SMTP服务器信息
        self.send_user = conf.EMail.send_user.value
        self.password = conf.EMail.password.value
        self.receive_user = conf.EMail.receive_user.value
        self.smtp_server = conf.EMail.smtp_server.value
        self.smtp_port = conf.EMail.smtp_port.value  # 163邮箱使用的SSL端口

    def send_email(self, subject, message):
        # 创建一个MIMEText对象来表示邮件
        email_message = MIMEText(message, "plain")
        email_message['From'] = formataddr((Header('DDQ Message', 'utf-8').encode(), self.send_user))
        email_message['To'] = formataddr((Header('收件人', 'utf-8').encode(), self.receive_user))
        email_message['Subject'] = Header(subject, 'utf-8')
        # 开始SMTP会话
        try:
            with smtplib.SMTP_SSL(self.smtp_server, self.smtp_port) as server:
                server.login(self.send_user, self.password)
                server.sendmail(self.send_user, [self.receive_user], email_message.as_string())
                msg = [True, f"邮件发送成功： {subject}"]
        except smtplib.SMTPException as e:
            msg = [False, f"邮件发送失败，错误信息：{e}"]

        return msg


class CheckButtonContainer:
    def __init__(self):
        self.n = 5
        self.label_frame: tk.Frame = None
        self.frame: tk.Frame = None
        self.options_dc = {}

    def set_frame(self, label_frame: tk.Frame):
        self.label_frame = label_frame
        self.frame = tk.Frame(self.label_frame, width=conf.View.widget_width.value)

    def set_cont(self, options: list, default_indices: list):
        count = 0
        # 初始化
        self.options_dc = {}
        for widget in self.frame.winfo_children():
            widget.destroy()
        # options为字典，包含键：选项名，值：选项对应boolean var
        for o in options:
            self.options_dc[o] = tk.BooleanVar()
        for option, var_0 in self.options_dc.items():
            row_0 = count // self.n
            column_0 = count % self.n
            # 设置默认值
            if count in default_indices:
                var_0.set(True)
            else:
                var_0.set(False)
            cb = tk.Checkbutton(self.frame, text=option[:4] + '.' if len(option) > 5 else option, variable=var_0, width=5)
            cb.grid(row=row_0, column=column_0, padx=1, pady=1)
            count += 1

    def get_cont(self):
        options = []
        default_indices = []
        for index, (option, var_0) in enumerate(self.options_dc.items()):
            options.append(option)
            if var_0.get():
                default_indices.append(index)
        return options, default_indices


@dataclass
class DataServerJson:
    """数据端口配置类"""
    software: str = "DDQ"
    data_flow_mode: DataFlowMode = DataFlowMode.His
    variety: Variety = Variety.Stock
    symbol_ls: list[str] = None
    interval: Interval = Interval.MINUTE
    count: int = 0
    start_time: str = ""
    end_time: str = ""
    set_time: str = ""
    file_name: str = ""

    def to_json_dc(self):
        json_dc = {}
        for attr in self.__dict__:
            value = getattr(self, attr)
            json_dc[attr] = str(value) if isinstance(value, Enum) else value
        return json_dc
        # return jsonpickle.encode(json_dc, unpicklable=True, make_refs=False)

    def to_attr(self, json_dc_encode):
        json_dc = jsonpickle.decode(json_dc_encode)
        for name, value in json_dc.items():
            setattr(self, name, value)

    def save_json(self, conf_path):
        with open(conf_path, 'w', encoding='utf-8') as file:
            json.dump(self.to_json_dc(), file, ensure_ascii=False, indent=4)

    def read_json(self, conf_path):
        # 读取json字符串中的内容，并将当前实例内容进行更新
        with open(conf_path, 'r', encoding='utf-8') as file:
            json_dc = json.load(file)
        self.to_attr(json_dc)


@dataclass
class TradeServerJson:
    """交易端口接口类"""
    software: str = "DDQ"
    account_id: str = ""
    account: AccountData = None
    position_dc: dict[str, PositionData] = None
    order_dc: dict[str, OrderData] = None

    def to_json_dc(self):
        json_dc = {}
        for attr in self.__dict__:
            value = getattr(self, attr)
            json_dc[attr] = value

        return jsonpickle.encode(json_dc, unpicklable=True, make_refs=False)

    def to_attr(self, json_dc_encode):
        json_dc = jsonpickle.decode(json_dc_encode)
        for name, value in json_dc.items():
            setattr(self, name, value)

    def save_json(self, conf_path):
        with open(conf_path, 'w', encoding='utf-8') as file:
            json.dump(self.to_json_dc(), file, ensure_ascii=False, indent=4)

    def read_json(self, conf_path):
        # 读取json字符串中的内容，并将当前实例内容进行更新
        with open(conf_path, 'r', encoding='utf-8') as file:
            json_dc = json.load(file)
        self.to_attr(json_dc)


